package com.atguigu.sink;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.serialization.SimpleStringEncoder;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.connector.source.util.ratelimit.RateLimiterStrategy;
import org.apache.flink.configuration.MemorySize;
import org.apache.flink.connector.datagen.source.DataGeneratorSource;
import org.apache.flink.connector.datagen.source.GeneratorFunction;
import org.apache.flink.connector.file.sink.FileSink;
import org.apache.flink.core.fs.Path;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.filesystem.OutputFileConfig;
import org.apache.flink.streaming.api.functions.sink.filesystem.bucketassigners.DateTimeBucketAssigner;
import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.DefaultRollingPolicy;

import java.time.Duration;
import java.time.ZoneId;
import java.util.TimeZone;

/**
 * @author gmd
 * @desc 输出到文件
 * @since 2024-11-26 10:12:32
 */
public class SinkFile {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 每个目录中，都有 并行度个数的 文件在写入
        env.setParallelism(2);

        // 开启检查点，在作业出现故障时恢复数据流，确保数据的可靠性。
        // CheckpointingMode.EXACTLY_ONCE 表示采用精确一次（Exactly Once），即保证每条数据只处理一次，不会出现重复处理或丢失的情况。
        // 如果没有设置检查点，文件可能一直处于“进行中”状态，.inprogress文件后缀就一直都存在。
        env.enableCheckpointing(2000, CheckpointingMode.EXACTLY_ONCE);

        DataGeneratorSource<String> dataGeneratorSource = new DataGeneratorSource<>(
                new GeneratorFunction<Long, String>() {
                    @Override
                    public String map(Long value) throws Exception {
                        return "Number:" + value;
                    }
                },
                Long.MAX_VALUE,
                RateLimiterStrategy.perSecond(1000),
                Types.STRING
        );
        DataStreamSource<String> dataGen = env.fromSource(dataGeneratorSource, WatermarkStrategy.noWatermarks(), "data-generator");

        // 输出到文件系统
        FileSink<String> fieSink = FileSink
                // 输出行式存储的文件，指定路径、指定编码
                .<String>forRowFormat(new Path("E:\\test"), new SimpleStringEncoder<>("UTF-8"))
                // 输出文件的一些配置： 文件名的前缀、后缀
                .withOutputFileConfig(
                        OutputFileConfig.builder()
                                .withPartPrefix("atguigu-")
                                .withPartSuffix(".log")
                                .build()
                )
                // 按照目录分桶：如下，就是每个小时一个目录
                .withBucketAssigner(new DateTimeBucketAssigner<>("yyyy-MM-dd HH", ZoneId.systemDefault()))
                // 文件滚动策略: 每10秒或文件超过100KB滚动
                .withRollingPolicy(
                        DefaultRollingPolicy.builder()
                                .withRolloverInterval(Duration.ofSeconds(10))
                                .withMaxPartSize(new MemorySize(1024*100))
                                .build()
                )
                .build();

        // 输出到文件
        dataGen.sinkTo(fieSink);
        // 异步执行，env.execute();
        env.executeAsync();
        // 30秒后结束程序
        Thread.sleep(30*1000);
        System.exit(0);
    }

    /**
     * [文件前缀]-[UUID]-[分区编号].[文件后缀].inprogress-[UUID2]
     * 文件前缀（如 .atguigu-）
     * 文件的唯一标识符（UUID）
     * 分区编号（如 -0）
     * 文件后缀（如 .log）
     * .inprogress 表示文件正在被写入，写入完成后该后缀会去掉。
     * 因此，你看到的文件名 atguigu--deb42114-8a94-4f37-b576-63dc0e7335c5-0.log.inprogress.337dca41-11e0-4cbd-abf2-fdda1657c807 只是 Flink 在正在写入时的临时状态，最终会转换为一个没有 .inprogress 后缀的文件。
     */

}
